<?php

namespace Zeke\Net\Http\Stream;


use Zeke\Contracts\Io\Stream\ReadableStreamContracts;
use Zeke\Contracts\Io\Stream\WritableStreamContracts;
use Zeke\Zee\Event\EventEmitter;
use Psr\Http\Message\StreamInterface;
use Zeke\Zee\Util;

/**
 * [Internal] Bridge between StreamInterface from PSR-7 and ReadableStreamInterface from ReactPHP
 *
 * This class is used in the server to stream the body of an incoming response
 * from the client. This allows us to stream big amounts of data without having
 * to buffer this data. Similarly, this used to stream the body of an outgoing
 * request body to the client. The data will be sent directly to the client.
 *
 * Note that this is an internal class only and nothing you should usually care
 * about. See the `StreamInterface` and `ReadableStreamInterface` for more
 * details.
 *
 * @see StreamInterface
 * @see ReadableStreamInterface
 */
class HttpBodyStream extends EventEmitter implements StreamInterface, ReadableStreamContracts
{
    /**
     * @var ReadableStreamContracts
     */
    public $input;

    /**
     * @var bool
     */
    private $closed = false;

    /**
     * body 长度
     * @var int|null
     */
    private $size;

    /**
     * 构造器
     * @param ReadableStreamContracts $input Stream data from $stream as a body of a PSR-7 object4
     * @param int|null $size size of the data body
     */
    public function __construct(ReadableStreamContracts $input, $size)
    {
        $this->input = $input;
        $this->size = $size;

        //注册事件回调
        $this->input->on('data', array($this, 'handleData'));
        $this->input->on('end', array($this, 'handleEnd'));
        $this->input->on('error', array($this, 'handleError'));
        $this->input->on('close', array($this, 'close'));
    }


    /**
     * 是否可读
     *
     * @return bool
     *
     * @author <liu.bin@ruaby.com> 2018-08-24 00:06
     */
    public function isReadable()
    {
        return !$this->closed && $this->input->isReadable();
    }

    /**
     * 暂停
     *
     * @author <liu.bin@ruaby.com> 2018-08-24 00:06
     */
    public function pause()
    {
        $this->input->pause();
    }


    /**
     * 恢复
     *
     * @author <liu.bin@ruaby.com> 2018-08-24 00:06
     */
    public function resume()
    {
        $this->input->resume();
    }


    /**
     * 数据传递，将读数据流转到可写数据流
     *
     * @param WritableStreamContracts $dest
     * @param array $options
     * @return WritableStreamContracts
     *
     * @author <liu.bin@ruaby.com> 2018-08-24 00:06
     */
    public function pipe(WritableStreamContracts $dest, array $options = array())
    {
        Util::pipe($this, $dest, $options);

        return $dest;
    }


    /**
     * 关闭
     * Closes the stream and any underlying resources.
     *
     * @author <liu.bin@ruaby.com> 2018-08-24 00:06
     */
    public function close()
    {
        if ($this->closed) {
            return;
        }
        $this->closed = true;
        $this->input->close();
        $this->emit('close');
        $this->removeAllListeners();
    }

    /**
     * 获取body大小
     *
     * @return int|null
     *
     * @author <liu.bin@ruaby.com> 2018-08-24 00:06
     */
    public function getSize()
    {
        return $this->size;
    }


    /**
     * Reads all data from the stream into a string, from the beginning to end.
     *
     * This method MUST attempt to seek to the beginning of the stream before
     * reading data and read the stream until the end is reached.
     *
     * Warning: This could attempt to load a large amount of data into memory.
     *
     * This method MUST NOT raise an exception in order to conform with PHP's
     * string casting operations.
     *
     * 输出body数据
     * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring
     * @return string
     * @ignore
     */
    public function __toString()
    {
        return '';
    }


    /**
     *
     * Separates any underlying resources from the stream.
     *
     * After the stream has been detached, the stream is in an unusable state.
     *
     * @return resource|null Underlying PHP stream, if any
     * @return null|resource
     *
     * 分割流数据
     * @author <liu.bin@ruaby.com> 2018-08-28 11:34
     * @ignore
     */
    public function detach()
    {
        return null;
    }


    /**
     * 读写的当前位置
     * Returns the current position of the file read/write pointer
     *
     * @return int|void
     *
     * @author <liu.bin@ruaby.com> 2018-08-28 11:34
     * @ignore
     */
    public function tell()
    {
        throw new \BadMethodCallException();
    }

    /**
     * body是否接受完整
     * Returns true if the stream is at the end of the stream.
     *
     * @return bool|void
     *
     * @author <liu.bin@ruaby.com> 2018-08-28 11:35
     */
    public function eof()
    {
        throw new \BadMethodCallException();
    }


    /**
     * Returns whether or not the stream is seekable.
     *
     * @return bool
     *
     * @ignore
     * @author <liu.bin@ruaby.com> 2018-08-28 11:36
     */
    public function isSeekable()
    {
        return false;
    }


    /**
     * 重置当前数据流指针位置
     *
     * @param int $offset
     * @param int $whence
     *
     *
     * @author <liu.bin@ruaby.com> 2018-08-28 11:37
     * @ignore
     */
    public function seek($offset, $whence = SEEK_SET)
    {
        throw new \BadMethodCallException();
    }


    /**
     * Seek to the beginning of the stream.
     * 重置到起始位置
     *
     * @author <liu.bin@ruaby.com> 2018-08-28 11:38
     * @ignore
     */
    public function rewind()
    {
        throw new \BadMethodCallException();
    }

    /** @ignore */
    public function isWritable()
    {
        return false;
    }

    /** @ignore */
    public function write($string)
    {
        throw new \BadMethodCallException();
    }

    /** @ignore */
    public function read($length)
    {
        throw new \BadMethodCallException();
    }

    /** @ignore */
    public function getContents()
    {
        return '';
    }

    /** @ignore */
    public function getMetadata($key = null)
    {
        return null;
    }

    /** @internal */
    public function handleData($data)
    {
        $this->emit('data', array($data));
    }

    /** @internal */
    public function handleError(\Exception $e)
    {
        $this->emit('error', array($e));
        $this->close();
    }

    /** @internal */
    public function handleEnd()
    {
        if (!$this->closed) {
            $this->emit('end');
            $this->close();
        }
    }
}
